package com.rec.service.impl;

import com.api.client.VideoClient;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.constant.RedisKey;
import com.common.domain.dto.RecAddDTO;
import com.common.domain.dto.RecUpdateSortNumDTO;
import com.common.domain.po.Rec;
import com.common.result.Result;
import com.common.utils.CollectUtils;
import com.rec.mapper.RecMapper;
import com.rec.service.RecService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author 路亚
 * @version 0.1
 * @className RecServiceImpl
 * @description
 * @date 2024/12/13 21:03
 * @since jdk11
 */
@Service
@RequiredArgsConstructor
public class RecServiceImpl extends ServiceImpl<RecMapper, Rec> implements RecService {

    private final RecMapper recMapper;

    private final VideoClient videoClient;

    private final RedisTemplate<String, Object> redisTemplate;

    private final ThreadPoolTaskExecutor taskExecutor;
    /**
     * 添加推荐记录
     * 此方法用于批量添加新的推荐记录到数据库中它首先验证推荐表中的数据量，
     * 确保数据量在可接受范围内（例如，保存20个左右的记录）然后，它处理可能的重复序号情况，
     * 当序号重复时，根据ID进行排序最后，它将新记录批量保存到数据库中
     *
     * @param recAddDTOList 一个包含多个RecAddDTO对象的列表，代表待添加的推荐记录
     */
    @Override
    public void addRec(List<RecAddDTO> recAddDTOList) {
        // TODO: 验证（推荐表中的数据不应该太多，保存20个左右）
        // 序号可以重复，当序号重复时，再根据id进行排序
        // 如果序号可以重复，那就可以随意批量插入，但是要保证id不重复
        List<Rec> list = list();
        Map<Long, Rec> longRecMap = CollectUtils.toHashMap(list, Rec::getId);
        List<Rec> recList = new ArrayList<>();
        for (RecAddDTO recAddDTO : recAddDTOList) {
            if (!longRecMap.containsKey(recAddDTO.getId())){
                Rec rec = new Rec();
                BeanUtils.copyProperties(recAddDTO, rec);
                recList.add(rec);
            }
        }
        taskExecutor.execute(() -> {
            redisTemplate.delete(RedisKey.REC_LIST_KEY);
            redisTemplate.delete(RedisKey.REC_LIST_BY_CAROUSEL);
        });
        saveBatch(recList);
    }

    /**
     * 更新推荐信息的排序编号
     * 该方法使用了事务注解，确保整个操作的原子性
     *
     * @param dtoList 包含需更新的推荐信息ID和新的排序编号的列表
     */
    @Override
    @Transactional
    public void updateSortNum(List<RecUpdateSortNumDTO> dtoList) {
        // 批量修改，如果循环调用数据库会导致性能问题，这里采用另一种方法
        // 1.先获取所有待修改的记录
        List<Long> idList = dtoList.stream().map(RecUpdateSortNumDTO::getId).collect(Collectors.toList());
        List<Rec> recList = listByIds(idList);
        // 2.删除所有待修改的记录
        removeByIds(idList);
        // 3.重新添加所有待修改的记录
        List<Rec> newRecList = new ArrayList<>();
        for (RecUpdateSortNumDTO dto : dtoList) {
            for (Rec rec : recList) {
                if (rec.getId().equals(dto.getId())){
                    rec.setSortNum(dto.getSortNum());
                    newRecList.add(rec);
                }
            }
        }
        taskExecutor.execute(() -> {
            redisTemplate.delete(RedisKey.REC_LIST_KEY);
            redisTemplate.delete(RedisKey.REC_LIST_BY_CAROUSEL);
        });
        saveBatch(newRecList);
    }

    /**
     * 查询所有推荐的短剧信息
     *
     * 此方法首先查询所有推荐记录的ID，然后批量查询这些短剧的详细信息
     * 使用Stream API来映射和收集推荐记录的ID，以提高查询效率
     *
     * @return 包含所有推荐短剧信息的结果对象
     */
    @Override
    public Result queryAll() {
        String key = RedisKey.REC_LIST_KEY;
        Long size = redisTemplate.opsForList().size(key);
        if (size != null && size > 0){
            return new Result(200, "OK", redisTemplate.opsForList().range(key, 0, -1));
        }
        // 查询所有推荐记录的ID
        List<Rec> list = list();
        List<Long> idList = list.stream().map(Rec::getId).collect(Collectors.toList());
        // 根据ID批量查询推荐的短剧信息
        Result result = videoClient.listByIds(idList);
        List data = (List) result.getData();
        taskExecutor.execute(() -> {
            redisTemplate.opsForList().rightPushAll(RedisKey.REC_LIST_KEY, (Collection) data);
        });
        return result;
    }

    /**
     * 查询所有推荐的短剧信息，并且这些短剧适用于轮播展示
     *
     * 此方法首先查询所有推荐记录的ID，然后过滤出适用于轮播的短剧记录的ID，
     * 最后批量查询这些短剧的详细信息使用Stream API进行过滤和映射，
     * 以提高查询效率并确保只查询符合条件的短剧信息
     *
     * @return 包含所有推荐并适用于轮播展示的短剧信息的结果对象
     */
    @Override
    public Result queryAllByCarousel() {
        String key = RedisKey.REC_LIST_BY_CAROUSEL;
        Long size = redisTemplate.opsForList().size(key);
        if (size != null && size > 0){
            return new Result(200, "OK", redisTemplate.opsForList().range(key, 0, -1));
        }
        // 查询所有推荐记录的ID
        List<Rec> list = list();
        List<Long> idList = list.stream()
                .filter(rec -> rec.getCarousel() == 1)
                .map(Rec::getId)
                .collect(Collectors.toList());
        // 根据ID批量查询推荐的短剧信息
        Result result = videoClient.listByIds(idList);
        List data = (List) result.getData();
        taskExecutor.execute(() -> {
            redisTemplate.opsForList().rightPushAll(RedisKey.REC_LIST_BY_CAROUSEL, (Collection) data);
        });
        return result;
    }
}
